import { strBase64Decode, strEncodeBase64 } from "../codec"
import { ValueError } from "../exceptions"
import { isNumber, isServer } from "../is"

declare type thingType = Record<string, number>
declare type IDType = 'number' | 'string' | 'simple' | 'baseb4'

/**
 * @since v0.0.18
 */
class IdManager {
  /**事件容器 */
  private _things: thingType = {}
  private _STORAGE_KEY_: string = '__JC_UTILS_ID_MANAGER_STORYRA_KET__'

  /**获取 LOCAL STORAGE 键*/
  public get storageKey() {
    return this._STORAGE_KEY_;
  }

  /**设置 LOCAL STORAGE 键*/
  public set storageKey(key: string) {
    this._STORAGE_KEY_ = key;
  }

  /**
   * 保存到本地存储
   * - 你可以通过 getter storageKey 设置存储键。
   * @param {undefined|string} path 如果指定了此参数，且在服务端环境，则会保存到以该参数为路径的位置而非 localstorage，否则保存到localstorage
   */
  public async save(path?:string): Promise<boolean> {
    if(path && isServer()){
      const fs = await import('fs');
      
      let _ = {} as any;
      try{
        _[this._STORAGE_KEY_] = JSON.stringify(this._things);
        fs.writeFileSync(path,JSON.stringify(_))
        return true
      }catch(e){
        throw new Error(`${e}`)
      }
    }else{
      try{
        localStorage.setItem(this._STORAGE_KEY_, JSON.stringify(this._things))
        return Promise.resolve(true);
      }catch(e){
        console.warn(e);
        return Promise.resolve(false)
      }
    }
  }

  /**
   * 从本地存储中加载
   * @param {undefined|string} from 如果指定了此参数且在服务端环境，则从该参数表示的路径加载数据而不是 localstorage
   * @returns 当加载成功时返回 true，否则返回 false
   */
  public async load(from?:string): Promise<boolean> {
    if(from && isServer()){
      try{
        const fs = await import('fs');
        this._things = JSON.parse(JSON.parse(fs.readFileSync(from).toString())[this._STORAGE_KEY_]);
        return true
      }catch(e){
        throw new Error(`${e}`)
      }
    }else{
      const s = localStorage.getItem(this._STORAGE_KEY_);
      if (s) {
        try {
          this._things = JSON.parse(s);
          return Promise.resolve(true);
        } catch (e) {
          console.warn(e);
          return Promise.resolve(false)
        }
      }
    }
    return Promise.resolve(false)
  }

  /**
   * 新建事物
   * @param thingName 事物名
   * @param begin id 计数起始
   * @returns 是否新建成功
   */
  public addThing(thingName: string, begin: number = 0): boolean {
    if (thingName === '') {
      throw ValueError('thingName can\'t be an empty string.')
    }
    if (begin < 0) {
      throw ValueError('The value of "begin" must be greater than 0.')
    }
    if (this._things[thingName]) {
      console.warn(`Thing name ${thingName} has already exists.`);
      return false
    } else {
      this._things[thingName] = begin;
      return true
    }
  }

  /**
   * 返回事物是否存在
   * @param thingName 事物名
   * @since v0.0.19
   */
  public hasThing(thingName: string): boolean {
    if (this._things[thingName]) {
      return true
    }
    return false
  }

  /**
   * 删除事物
   * @param thingName 事物名
   * @returns 删除成功则返回 true，反之返回 false
   * @since v0.0.18
   */
  public dropThing(thingName: string): boolean {
    if (this._things.hasOwnProperty(thingName)) {
      delete this._things[thingName];
      return true
    } else {
      console.warn(`Thing name ${thingName} does not exists.`);
      return false
    }
  }

  /**
   * 为指定事物申请一个新 ID
   * @param thingName 事物名
   * @returns id 字符串
   */
  public getNewItemId(thingName: string, idType: IDType = "baseb4"): string | number {
    if (this._things.hasOwnProperty(thingName)) {
      this._things[thingName] = this._things[thingName] + 1;
      return this._returnUtil(thingName, this._things[thingName], idType)
    } else {
      throw ValueError(`Thing name ${thingName} does not exists.`)
    }
  }
  /**
   * 获取某个事物当前的ID
   * @param thingName 事物名
   * @returns 最后给出的一个 id
   */
  public getCurrentId(thingName: string, idType: IDType = "baseb4"): string | number {
    if (this._things.hasOwnProperty(thingName)) {
      return this._returnUtil(thingName, this._things[thingName], idType)
    }
    else {
      throw ValueError(`Thing name ${thingName} does not exists.`)
    }
  }

  /**
   * 指定事件是否已有指定ID
   * @param {string} thingName 事件名
   * @param {number | string} id ID值
   * @param { IDType }idType ID类型
   * @returns { boolean } 如果有，则返回 true，否则返回 false
   */
  public hasID(thingName: string, id: number | string, idType: IDType = "baseb4", raiseWarn:boolean=false): boolean {
    const counter: number|undefined = this._things[thingName];
    if (isNumber(counter)) {
      if(isNumber(id)){
        if(counter>=id){
          return true
        }
      }else{
        let _idStr:string;
        if (idType === "baseb4") {
          _idStr = strBase64Decode(thingName).replace(new RegExp(`^${thingName}+-`), '')
        } else if (idType === "simple") {
          _idStr = thingName.replace(new RegExp(`^${thingName}+-`), '')
        }else{
          _idStr = id
        }
        try{
          let idNumber: number = parseInt(_idStr)
          if(counter>=idNumber){
            return true
          }
        }catch(e){
          if(raiseWarn){
            console.warn(e);
          }
          return false
        }
        
      }
    }
    return false
  }

  /**
   * 获取某个事物当前已经给出的 id 数量
   * @param thingName 事物名
   * @returns 某个事物当前已经给出的 id 数量
   */
  public countIds(thingName: string): number {
    if (this._things.hasOwnProperty(thingName)) {
      return this._things[thingName]
    }
    else {
      throw ValueError(`Thing name ${thingName} does not exists.`)
    }
  }

  private _returnUtil(thingName: string, rawId: number, idType: IDType) {
    if (idType === 'number') {
      return rawId
    }
    else if (idType === 'string') {
      return rawId.toString()
    }
    else if (idType === 'simple') {
      return thingName + '-' + rawId.toString()
    } else if (idType === 'baseb4') {
      return strEncodeBase64(thingName + '-' + rawId.toString())
    } else {
      throw ValueError(`Got an unexpected id type.`)
    }
  }
}

const idManager = new IdManager();


/**获取 LOCAL STORAGE 键 */
export function getStorageKey() {
  return idManager.storageKey;
}

/**
 * 设置 LOCAL STORAGE 键
 * @param key 存储键字符串
 */
export function setStorageKey(key: string) {
  if (key === "") {
    throw ValueError(`Got an empty string.`)
  }
  return idManager.storageKey = key;
}

/**
 * 从本地存储中加载
 * @param {undefined|string} from 如果指定了此参数且在服务端环境，则从该参数表示的路径加载数据而不是 localstorage
 */
export async function load(from?:string): Promise<boolean> {
  return idManager.load(from)
}

/**
 * 保存到本地存储
 * @param path — 如果指定了此参数，且在服务端环境，则会保存到以该参数为路径的位置而非 localstorage，否则保存到localstorage
 * - 你可以通过 setStorageKey 方法设置存储键
 */
export async function save(path?:string): Promise<boolean> {
  return idManager.save(path)
}

/**
 * 新建事物
 * @param thingName 事物名
 * @param begin id 计数起始
 * @returns 是否新建成功
 */
export function addThing(thingName: string, begin: number = 0): boolean {
  return idManager.addThing(thingName, begin)
}

/**
 * 删除事物
 * @param thingName 事物名
 * @returns 删除成功则返回 true，反之返回 false
 */
export function dropThing(thingName: string): boolean {
  return idManager.dropThing(thingName)
}

/**
 * 返回事物是否存在
 * @param thingName 事物名
 * @since v0.0.19
 */
export function hasThing(thingName: string): boolean {
  return idManager.hasThing(thingName)
}

/**
 * 指定事件是否已有指定ID
 * @param {string} thingName 事件名
 * @param {number | string} id ID值
 * @param { IDType }idType ID类型
 * @returns { boolean } 如果有，则返回 true，否则返回 false
 */
export function hasID(thingName: string, id: number | string, idType: IDType = "baseb4", raiseWarn:boolean=false): boolean{
  return idManager.hasID(thingName, id, idType, raiseWarn)
}
/**
 * 为指定事物申请一个新 ID
 * @param thingName 事物名
 * @returns id 字符串
 */
export function getNewItemId(thingName: string, idType: IDType = 'baseb4'): string | number {
  return idManager.getNewItemId(thingName, idType)
}

/**
 * 获取某个事物当前的ID
 * @param thingName 事物名
 * @returns 最后给出的一个 id
 */
export function getCurrentId(thingName: string, idType: IDType = 'baseb4') {
  return idManager.getCurrentId(thingName, idType)
}

/**
 * 获取某个事物当前已经给出的 id 数量
 * @param thingName 事物名
 * @returns 某个事物当前已经给出的 id 数量
 */
export function countIds(thingName: string) {
  return idManager.countIds(thingName)
}